﻿using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Serialization;

namespace SandCastleRunner
{
    class Runner
    {
        public const string xslTransformPath = @"\ProductionTools\XslTransform.exe";

        private SandCastleDirectory sandCastleDirectory;
        private string projectsWithDocumentation;
        private string documentationRootDirectoryPath;
        private string htmlHelpCompilerPath;
        private Project[] projects;
        private string baseDirectory;

        /// <summary>
        /// Starts the program.
        /// </summary>
        /// <param name="args">An array of strings.</param>
        static void Main(string[] args)
        {
            Runner runner = new Runner();
            runner.baseDirectory = Directory.GetCurrentDirectory();
            runner.Begin(args);
        }

        private void Begin(string[] args)
        {
            try
            {
                ValidateUserInput(args);
            }
            catch (ProjectException e)
            {
                ExitOnError(e.Message);
            }
            catch (DirectoryNotFoundException e)
            {
                ExitOnError(e.Message);
            }
            catch (FileNotFoundException e)
            {
                ExitOnError(e.Message);
            }
        }

        /// <summary>
        /// Validates the user input.
        /// </summary>
        /// <param name="args">An array of strings.</param>
        private void ValidateUserInput(string[] args)
        {
            if (args.Length != 4)
                ExitOnError("Usage: <executable> [sandcastle directory] [projects xml file] [documentation output directory] [HTML Help Compiler Path]");

            if (!Directory.Exists(args[0]))
                ExitOnError(args[0] + " is missing.");

            sandCastleDirectory = new SandCastleDirectory(args[0]);

            if (!File.Exists(args[1]))
                ExitOnError(args[1] + " is missing.");
            
            projectsWithDocumentation = args[1];

            if (!Directory.Exists(args[2]))
                ExitOnError(args[2] + " is missing.");
            
            documentationRootDirectoryPath = args[2];

            if (!File.Exists(args[3]))
                ExitOnError(args[3] + " is missing.");

            htmlHelpCompilerPath = args[3];

            GetProjects();
        }

        /// <summary>
        /// Get the projects from the xml file.
        /// </summary>
        private void GetProjects()
        {
            XmlSerializer serializer = new XmlSerializer(typeof(Project[]));
            FileStream fileStream = File.Open(projectsWithDocumentation, FileMode.Open);
            using (XmlReader reader = new XmlTextReader(fileStream))
            {
                if(!serializer.CanDeserialize(reader))
                    ExitOnError("Config file is invalid.");

                try
                {
                    projects = (Project[])serializer.Deserialize(reader);
                }
                catch (InvalidOperationException e)
                {
                    throw e.InnerException;
                }
            }

            foreach (Project project in projects)
                project.DocumentationRootDirectory = documentationRootDirectoryPath;

            BuildTheDocumentationDirectory();
        }

        /// <summary>
        /// Builds the documentation directory.
        /// </summary>
        private void BuildTheDocumentationDirectory()
        {
            foreach (Project project in projects)
                project.PrepareDocumentationDirectory();

            RunMrefBuilder();
        }

        /// <summary>
        /// Runs MRefBuilder.
        /// MRefBuilder uses reflection on the PE files.
        /// </summary>
        private void RunMrefBuilder()
        {
            string fullMrefBuilderPath = sandCastleDirectory.MrefBuilderPath;

            foreach (Project project in projects)
            {
                Directory.SetCurrentDirectory(baseDirectory);
                Directory.SetCurrentDirectory(project.DocumentationDirectory);
                
                string arguments = project.MainProjectPe + " /out:reflection.org";
                
                ProcessStartInfo mrefBuilderStartInfo = new ProcessStartInfo(fullMrefBuilderPath, arguments);
                mrefBuilderStartInfo.UseShellExecute = false;
                
                Process mrefBuilder = Process.Start(mrefBuilderStartInfo);
                mrefBuilder.WaitForExit();
            }
            TransformTheOutput();
        }

        /// <summary>
        /// Runs XslTransform.
        /// </summary>
        private void TransformTheOutput()
        {
            foreach (Project project in projects)
            {
                Directory.SetCurrentDirectory(baseDirectory);
                Directory.SetCurrentDirectory(project.DocumentationDirectory);
                
                string vsDocModel = "/xsl:" + sandCastleDirectory.GetXslTransformationPath(project.ProductionTransformXsl);
                string friendlyFileNames = "/xsl:" + sandCastleDirectory.GetXslTransformationPath("AddFriendlyFilenames.xsl");
                string arguments = vsDocModel + " reflection.org " + friendlyFileNames + " /out:reflection.xml";

                RunXslTranform(arguments);
            }
            GenerateTopicManifest();
        }

        /// <summary>
        /// Runs XslTransform again.
        /// </summary>
        private void GenerateTopicManifest()
        {
            foreach (Project project in projects)
            {
                Directory.SetCurrentDirectory(baseDirectory);
                Directory.SetCurrentDirectory(project.DocumentationDirectory);

                string topicManifestXslt = "/xsl:" + sandCastleDirectory.GetXslTransformationPath("ReflectionToManifest.xsl");
                string arguments = topicManifestXslt + " reflection.xml /out:manifest.xml";

                RunXslTranform(arguments);
            }
            BuildDirectoryStructure();
        }

        /// <summary>
        /// Builds the directory structure for HTML help files.
        /// </summary>
        private void BuildDirectoryStructure()
        {
            foreach (Project project in projects)
            {
                Directory.SetCurrentDirectory(baseDirectory);
                Directory.SetCurrentDirectory(project.DocumentationDirectory);

                string directoryBuilderPath = sandCastleDirectory.GetDirectoryBuilderPath(project.Presentation);

                ProcessStartInfo directoryBuilderStartInfo = new ProcessStartInfo(directoryBuilderPath);
                directoryBuilderStartInfo.UseShellExecute = false;

                Process directoryBuilder = Process.Start(directoryBuilderStartInfo);
                directoryBuilder.WaitForExit();
            }
            RunBuildAssembler();
        }

        /// <summary>
        /// Builds the HTML files.
        /// </summary>
        private void RunBuildAssembler()
        {
            foreach (Project project in projects)
            {
                Directory.SetCurrentDirectory(baseDirectory);
                Directory.SetCurrentDirectory(project.DocumentationDirectory);

                string buildAssemblerPath = sandCastleDirectory.BuildAssemblerPath;
                string configFilePath = "/config:" + sandCastleDirectory.GetConfigFilePath(project.Presentation);
                string arguments = configFilePath + " manifest.xml";

                ProcessStartInfo buildAssemblerStartInfo = new ProcessStartInfo(buildAssemblerPath, arguments);
                buildAssemblerStartInfo.UseShellExecute = false;

                Process buildAssembler = Process.Start(buildAssemblerStartInfo);
                buildAssembler.WaitForExit();
            }
            MakeTableOfContents();
        }

        /// <summary>
        /// Make the table of contents.
        /// </summary>
        private void MakeTableOfContents()
        {
            foreach (Project project in projects)
            {
                Directory.SetCurrentDirectory(baseDirectory);
                Directory.SetCurrentDirectory(project.DocumentationDirectory);

                string tableOfContentsXslt = "/xsl:" + sandCastleDirectory.GetXslTransformationPath(project.TableOfContentsXsl);
                string arguments = tableOfContentsXslt + " reflection.xml /out:toc.xml";

                RunXslTranform(arguments);
            }

            MakeTheChmProjectFiles();
        }

        /// <summary>
        /// Make the project files used for CHM generation.
        /// </summary>
        private void MakeTheChmProjectFiles()
        {
            foreach (Project project in projects)
            {
                Directory.SetCurrentDirectory(baseDirectory);
                Directory.SetCurrentDirectory(project.DocumentationDirectory);

                string chmBuilderPath = sandCastleDirectory.ChmBuilderPath;
                string arguments = "/html:output/html /toc:toc.xml /project:" + project.ProjectName;

                ProcessStartInfo chmBuilderStartInfo = new ProcessStartInfo(chmBuilderPath, arguments);
                chmBuilderStartInfo.UseShellExecute = false;

                Process chmBuilder = Process.Start(chmBuilderStartInfo);
                chmBuilder.WaitForExit();
            }
            CopyIconsAndStuff();
        }

        /// <summary>
        /// Copies the icons to the CHM directory.
        /// </summary>
        private void CopyIconsAndStuff()
        {
            foreach (Project project in projects)
            {
                Directory.SetCurrentDirectory(baseDirectory);
                Directory.SetCurrentDirectory(project.DocumentationDirectory);

                string[] directories = Directory.GetDirectories("output");
                foreach (string directory in directories)
                {
                    string chmDirectory = directory.Replace("output", "Chm");

                    if (!Directory.Exists(chmDirectory))
                        Directory.Move(directory, chmDirectory);
                }
            }
            RunHtmlHelpCompiler();
        }

        /// <summary>
        /// Make the CHM file.
        /// </summary>
        private void RunHtmlHelpCompiler()
        {
            foreach (Project project in projects)
            {
                Directory.SetCurrentDirectory(baseDirectory);
                Directory.SetCurrentDirectory(project.DocumentationDirectory);
                Directory.SetCurrentDirectory("Chm");

                string arguments = project.ProjectName + ".hhp";

                ProcessStartInfo htmlHelpCompilerStartInfo = new ProcessStartInfo(htmlHelpCompilerPath, arguments);
                htmlHelpCompilerStartInfo.UseShellExecute = false;

                Process htmlHelpCompiler = Process.Start(htmlHelpCompilerStartInfo);
                htmlHelpCompiler.WaitForExit();
            }
            CleanFolders();
        }

        /// <summary>
        /// Remove all other files.
        /// </summary>
        private void CleanFolders()
        {
            foreach (Project project in projects)
            {
                Directory.SetCurrentDirectory(baseDirectory);

                string[] filesToDelete = Directory.GetFiles(project.DocumentationDirectory);
                foreach (string fileToDelete in filesToDelete)
                    File.Delete(fileToDelete);

                string oldChmFilePath = project.DocumentationDirectory + @"\Chm\" + project.ProjectName + ".chm";
                string newChmFilePath = project.DocumentationDirectory + @"\" + project.ProjectName + ".chm";
                File.Copy(oldChmFilePath, newChmFilePath);

                string[] directoriesToDelete = Directory.GetDirectories(project.DocumentationDirectory);
                foreach (string directoryToDelete in directoriesToDelete)
                    Directory.Delete(directoryToDelete, true);
            }
        }

        /// <summary>
        /// Runs XSLTransform.
        /// </summary>
        /// <param name="arguments">A string.</param>
        private void RunXslTranform(string arguments)
        {
            string fullXslTransformPath = sandCastleDirectory.XslTransformPath;
            ProcessStartInfo xslTransformStartInfo = new ProcessStartInfo(fullXslTransformPath, arguments);
            xslTransformStartInfo.UseShellExecute = false;

            Process xslTransform = Process.Start(xslTransformStartInfo);
            xslTransform.WaitForExit();
        }

        /// <summary>
        /// Exits the program.
        /// </summary>
        /// <param name="errorMessage">A string.</param>
        private void ExitOnError(string errorMessage)
        {
            int errorExitCode = 1;
            Console.WriteLine(errorMessage);
            Environment.Exit(errorExitCode);
        }
    }
}
